﻿///////////////////////////////////////////////////////
//
// XDesigner.ORM 
//
// autohr : yuans
//
// date: 2011-2-23
//
// email  : yyf9989@hotmail.com
// 
//
///////////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using XDesigner.ORM.DOM;
using System.Data ;
using XDesigner.Common;
using System.Collections ;

namespace XDesigner.ORM
{
    public class ORMEngine : IDisposable 
    {
        private static System.LocalDataStoreSlot myDataStoreSlot
            = System.Threading.Thread.AllocateNamedDataSlot(Guid.NewGuid().ToString());

        /// <summary>
        /// Get local instance of current thread
        /// </summary>
        public static ORMEngine LocalInstance
        {
            get
            {
                return System.Threading.Thread.GetData(myDataStoreSlot) as ORMEngine;
            }
        }

        private static ORMEngine myGlobalInstance = new ORMEngine();

        /// <summary>
        /// Get global instance of Engine
        /// </summary>
        public static ORMEngine MyGlobalInstance
        {
            get
            {
                return myGlobalInstance; 
            }
        }

        private static System.LocalDataStoreSlot myContextSlot
            = System.Threading.Thread.AllocateNamedDataSlot(Guid.NewGuid().ToString());
        /// <summary>
        /// Get current ORM context 
        /// </summary>
        public static ORMContext CurrentContext
        {
            get
            {
                return System.Threading.Thread.GetData(myContextSlot) as ORMContext;
            }
        }

        internal static void SetCurrentContext(ORMContext context)
        {
            System.Threading.Thread.SetData(myContextSlot, context);
        }

        public ORMEngine()
        {
            System.Threading.Thread.SetData(myDataStoreSlot, this);
            this.Buffer.IsGlobal = true;
        }

        public ORMEngine(System.Data.IDbConnection connection)
        {
            _DataBase.Connection = connection;
            this.Buffer.IsGlobal = true;
        }

        private string _Name = "StandardORM";

        public string Name
        {
            get
            {
                return _Name; 
            }
            set
            {
                _Name = value; 
            }
        }

        private ORMProjectInfo _Project = new ORMProjectInfo();
        /// <summary>
        /// ORM-DOM document
        /// </summary>
        public ORMProjectInfo Project
        {
            get
            { 
                return _Project;
            }
            set
            {
                _Project = value;
            }
        }

        private ORMEventHandlerList _CustomHandles = new ORMEventHandlerList();
        [System.ComponentModel.Browsable( false )]
        public ORMEventHandlerList CustomHandles
        {
            get
            {
                return _CustomHandles; 
            }
        }

        public object ExecuteCustomHandler(string name, object instance, ORMPropertyInfo property)
        {
            return this.CustomHandles.ExecuteCustomHandler( name, instance, property);
        }

        private ORMTypeMappingHelperContainer _MappingHelpers = new ORMTypeMappingHelperContainer();

        public ORMTypeMappingHelperContainer MappingHelpers
        {
            get 
            {
                _MappingHelpers.Engine = this;
                return _MappingHelpers; 
            }
        }
         
        private ORMInstanceBuffer _Buffer = new ORMInstanceBuffer();

        public ORMInstanceBuffer Buffer
        {
            get
            {
                return _Buffer; 
            }
        }

        private ORMConfig _Config = new ORMConfig();

        public ORMConfig Config
        {
            get
            {
                if (_Config == null)
                {
                    _Config = new ORMConfig();
                }
                return _Config; 
            }
            set
            {
                _Config = value; 
            }
        }

        public ParameterStyle ParameterStyle
        {
            get
            {
                return this.Config.ParameterStyle;
            }
        }

        private ORMEventSender _EventSender = new ORMEventSender();
        /// <summary>
        /// ORM event sender
        /// </summary>
        public ORMEventSender EventSender
        {
            get 
            {
                _EventSender.Engine = this;
                return _EventSender; 
            }
            set
            {
                _EventSender = value; 
            }
        }

        public ORMTypeInfo AddType(Type instanceType)
        {
            ORMTypeInfo info = this.Project.AddType(instanceType);
            return info;
        }

        public void AddTypes(Type[] instanceTypes)
        {
            this.Project.AddTypes(instanceTypes);
        }

        #region Data base connection handle

        private ORMDataBase _DataBase = new ORMDataBase();
        /// <summary>
        /// Data base handler
        /// </summary>
        public ORMDataBase DataBase
        {
            get { return _DataBase; }
            set { _DataBase = value; }
        }


        public event ORMEventHandler CloseConnectionEvent = null;

        public virtual void CloseConnection()
        {
            this.DataBase.Close();
            if (CloseConnectionEvent != null)
            {
                ORMEventArgs args = new ORMEventArgs( this , ORMEventStyle.CloseConnection ); 
                CloseConnectionEvent(this, args );
            }
        }

        public event ORMEventHandler OpenConnectionEvent = null;

        public virtual bool OpenConnection()
        {
            if (OpenConnectionEvent != null)
            {
                ORMEventArgs args = new ORMEventArgs( this , ORMEventStyle.OpenConnection );
                OpenConnectionEvent(this, args );
                if( args.Cancel )
                {
                    return false ;
                }
                if (args.Result is System.Data.IDbConnection)
                {
                    this.DataBase.Connection = ( IDbConnection ) args.Result;
                }
            }
            this.DataBase.Open();
            return true;
        }

        private void CheckConnection()
        {
            if (this.DataBase.IsOpen == false)
            {
                OpenConnection();
                if (this.DataBase.IsOpen == false )
                {
                    throw new InvalidOperationException("No connection");
                }
            }
        }

        public IDbCommand CreateDBCommand()
        {
            CheckConnection();
            return this.DataBase.CreateCommand();
        }

        #endregion

        internal bool IsBuffered(ORMTypeInfo type)
        {
            if (type.Buffered == ORMBooleanValue.False)
            {
                return false;
            }
            if (this.Config.BufferLevel == ORMBufferLevel.Both)
            {
                return true;
            }
            else
            {
                if (type.Buffered == ORMBooleanValue.True)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }

        private Dictionary<string, int> myIncreaseIDs
            = new Dictionary<string, int>();

        public int GenerateIncreaseID(
            ORMContext context ,
            string tableName ,
            string keyFieldName )
        {
            if (tableName == null 
                || tableName.Trim().Length == 0)
            {
                throw new ArgumentNullException("name");
            }
            lock (myIncreaseIDs)
            {
                tableName = tableName.Trim().ToLower();
                if (myIncreaseIDs.ContainsKey(tableName))
                {
                    int v = myIncreaseIDs[tableName];
                    v++;
                    myIncreaseIDs[tableName] = v;
                    return v;
                }
                else
                {
                    string strSQL = "Select Max( "
                                    + keyFieldName + " ) + 1 From "
                                    + tableName;
                    context.Command.CommandText = strSQL;
                    context.Command.Parameters.Clear();
                    this.BeforeExecuteCommand(context, context.Command);

                    int intIncreaseID = 1;

                    object id = context.Command.ExecuteScalar();
                    if (id != null && DBNull.Value.Equals(id) == false)
                    {
                        intIncreaseID = Convert.ToInt32(id);
                    }
                    myIncreaseIDs[tableName] = intIncreaseID;
                    return intIncreaseID;
                }
            }//lock
        }

        public ORMBindingSource CreateBindingSource(
            Type instanceType ,
            IEnumerable instances)
        {
            ORMBindingSource bs = new ORMBindingSource();
            bs.LogItemChange = false;
            bs.Engine = this;
            bs.ORMType = this.Project.Types[instanceType];
            foreach (object obj in instances)
            {
                bs.Add(obj);
            }
            bs.LogItemChange = true;
            return bs;
        }

        //public object CreateInstanceFast(Type type)
        //{
        //    if (type == null)
        //    {
        //        throw new ArgumentNullException("type");
        //    }
        //    ORMTypeInfo ot = this.Project.Types[type];
        //    if (ot == null)
        //    {
        //        throw new NotSupportedException(type.FullName);
        //    }
        //    ORMTypeMappingHelper helper = this.MappingHelpers[ot];
        //    return helper.CreateInstance();
        //}

        /// <summary>
        /// Create a instance , maby generate key value.
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public object CreateInstance(Type type)
        {
            if (type == null)
            {
                throw new ArgumentNullException("type");
            }
            ORMTypeInfo ot = this.Project.Types[type];
            if (ot == null)
            {
                throw new NotSupportedException(type.FullName);
            }
            return CreateInstance(ot);
        }

        /// <summary>
        /// Create a instance , maby generate key value.
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public virtual object CreateInstance(ORMTypeInfo type)
        {
            if (type == null)
            {
                throw new ArgumentNullException("type");
            }
            ORMTypeMappingHelper helper = this.MappingHelpers[type];
            object instance = helper.CreateInstance();
            if (instance is IORMInstance)
            {
                IORMInstance oi = (IORMInstance)instance;
                oi.ORMDataState = DataRowState.Added;
                oi.ORMEngine = this;
            }
            ORMContext context = new ORMContext(this);
            SetCurrentContext( context );
            helper.GenerateNewValue(context, instance);
            SetCurrentContext(null);
            ORMPerformanceCounter.IncreaseCreate( type.InstanceType );
            return instance;
        }

        public virtual void BeforeExecuteCommand(ORMContext context , ORMDBCommand command)
        {
            if (this.Config.DebugMode)
            {
                command.DebugPrint(this.Name);
            }
        }

        public object ExecuteScalar(ORMDBCommand cmd)
        {
            cmd.Engine = this;
            this.BeforeExecuteCommand(null, cmd);
            ORMPerformanceCounter.Increment("ExecuteScalar");
            ORMPerformanceCounter.Increment(ORMPerformanceCounter.Name_ExecuteCommand);
            object result = cmd.ExecuteScalar();
            return result;
        }

        #region Read instance from data base 


        public Array ReadInstances(Type instanceType ,QueryConditionList conditions )
        {
            if (instanceType == null)
            {
                throw new ArgumentNullException("instanceType");
            }
            ORMTypeInfo ot = this.Project.Types[instanceType];
            if (ot == null)
            {
                throw new NotSupportedException(instanceType.FullName);
            }
            if (ot.Enabled == false)
            {
                return null;
            }

            ORMTypeMappingHelper helper = this.MappingHelpers[ot];
            if (helper == null)
            {
                throw new NotSupportedException(instanceType.FullName + " Helper");
            }
            using (ORMDBCommand cmd = new ORMDBCommand(this))
            {
                string strSQL = helper.SelectCommandText;
                string strWhere = ORMDBCommand.BuildConditionSQL(
                    conditions,
                    this.Config.ParameterStyle,
                    cmd);
                if (strWhere != null && strWhere.Length > 0)
                {
                    strSQL = strSQL + "\r\n Where " + strWhere;
                }
                if (Util.HasContent(helper.OrderCommandText))
                {
                    strSQL = strSQL + " Order By " + helper.OrderCommandText;
                }
                cmd.CommandText = strSQL;
                return ReadInstances(instanceType , cmd );
            }
        }

        public Array ReadInstances(Type instanceType , string commandText )
        {
            if (Util.HasContent(commandText))
            {
                using (ORMDBCommand cmd = new ORMDBCommand(this))
                {
                    cmd.CommandText = commandText;
                    return ReadInstances(instanceType , cmd );
                }
            }
            else
            {
                return null;
            }
        }

        public QueryConditionList CreateConditions(
            ORMContext context ,
            object instance, 
            IEnumerable conditionPropertyNames)
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }

            ORMTypeInfo ot = this.Project.Types[instance.GetType()];
            if (ot == null)
            {
                throw new NotSupportedException(instance.GetType().FullName);
            }
            if (ot.Enabled == false)
            {
                return null;
            }

            ORMTypeMappingHelper helper = this.MappingHelpers[ ot ];
            QueryConditionList list = new QueryConditionList();
            foreach (ORMPropertyInfo property in ot.Properties)
            {
                foreach (string name in conditionPropertyNames)
                {
                    if (string.Compare(name, property.Name, true) == 0)
                    {
                        QueryCondition item = new QueryCondition();
                        item.FieldName = property.RuntimeDBFieldName;
                        item.Name = property.Name;
                        item.Value = helper.GetDBPropertyValue( context , instance, property.Name);
                        if (item.Value is string)
                        {
                            string str = (string)item.Value;
                            if (str.StartsWith("%") || str.EndsWith("%"))
                            {
                                item.Method = "like";
                            }
                        }//if
                        list.Add(item);
                        break;
                    }//if
                }//foreach
            }//foreach

            return list;
        }

        private ORMStandardRelationManager _StandardRelationManager = new ORMStandardRelationManager();

        public ORMStandardRelationManager StandardRelationManager
        {
            get
            {
                _StandardRelationManager._Engine = this;
                return _StandardRelationManager; 
            }
        }

        public Array ReadInstancesByStandardRelation(
            object parentInstance, 
            Type childInstanceType)
        {
            return this.StandardRelationManager.ReadChildInstances(
                parentInstance,
                childInstanceType);
        }

        public Array ReadInstancesByCondition(
            object conditionInstance,
            System.Collections.IEnumerable conditionPropertyNames)
        {
            if (conditionInstance == null)
            {
                throw new ArgumentNullException("conditionInstance");
            }
            if (conditionPropertyNames == null)
            {
                throw new ArgumentNullException("conditionPropertyNames");
            }
            ORMTypeInfo ot = this.Project.Types[conditionInstance.GetType()];
            if (ot == null)
            {
                throw new NotSupportedException(conditionInstance.GetType().FullName);
            }
            if (ot.Enabled == false)
            {
                return null;
            }

            ORMTypeMappingHelper helper = this.MappingHelpers[ot];
            if (helper == null)
            {
                throw new NotSupportedException( conditionInstance.GetType().FullName + " Helper" );
            }
            using (ORMDBCommand cmd = new ORMDBCommand(this))
            {
                string strSQL = helper.SelectCommandText;
                ORMContext context =  new ORMContext(this);
                context.ParameterStyle = this.Config.ParameterStyle;
                QueryConditionList list = CreateConditions(
                    context ,
                    conditionInstance,
                    conditionPropertyNames );
                if (list != null && list.Count > 0)
                {
                    string strWhere = ORMDBCommand.BuildConditionSQL(
                        list,
                        this.Config.ParameterStyle, 
                        cmd);
                    if (strWhere != null && strWhere.Length > 0)
                    {
                        strSQL = strSQL + "\r\nWhere " + strWhere;
                    }
                }
                if (Util.HasContent(helper.OrderCommandText))
                {
                    strSQL = strSQL + " Order By " + helper.OrderCommandText;
                }
                cmd.CommandText = strSQL;
                return ReadInstances(conditionInstance.GetType() , cmd  );
            }
        }

        /// <summary>
        /// Detect a instance exist in database.
        /// </summary>
        /// <param name="instanceType"></param>
        /// <param name="keyValue"></param>
        /// <returns></returns>
        public bool ExistInstance(Type instanceType, object keyValue)
        {
            if (instanceType == null)
            {
                throw new ArgumentNullException("instanceType");
            }
            ORMTypeInfo ot = this.Project.Types[instanceType];
            if (ot == null)
            {
                throw new InvalidOperationException("ORMTypeInfo is null");
            }
            if (ot.Enabled == false)
            {
                return false;
            }
            if (ot.KeyProperty == null)
            {
                throw new InvalidOperationException("KeyProperty is null");
            }

            using( ORMDBCommand cmd = new ORMDBCommand( this ))
            {
                ORMContext context = new ORMContext( this );
                context.Command = cmd;
                ORMTypeMappingHelper helper = this.MappingHelpers[ot];
                StringBuilder mySQL = new StringBuilder();
                mySQL.Append(
                    "Select null from " + ot.TableName 
                    + " Where "
                    + ot.KeyProperty.RuntimeDBFieldName + " = ");
                ORMDBCommand.AppendParameterSQL(
                    mySQL,
                    ot.KeyProperty.RuntimeDBFieldName,
                    keyValue,
                    this.Config.ParameterStyle,
                    cmd);
                IDataReader reader = cmd.ExecuteReader(
                    CommandBehavior.SingleResult
                    | CommandBehavior.SingleRow);
                if (cmd.UserCancel)
                {
                    return false;
                }
                else
                {
                    bool result = reader.Read();
                    reader.Close();
                    return result;
                }
            }//using
        }

        public object ReadInstance(Type instanceType, object keyValue)
        {
            if (instanceType == null)
            {
                throw new ArgumentNullException("instanceType");
            }
            ORMTypeInfo ot = this.Project.Types[instanceType];
            if (ot == null)
            {
                throw new InvalidOperationException("ORMTypeInfo is null");
            }
            if (ot.Enabled == false)
            {
                return null;
            }
            if (ot.KeyProperty == null)
            {
                throw new InvalidOperationException("KeyProperty is null");
            }
            object result = this.Buffer.GetInstance(keyValue, instanceType);
            if (result != null)
            {
                return result;
            }
            using ( ORMDBCommand cmd = new ORMDBCommand( this ))
            {
                ORMContext context = new ORMContext( this );
                context.Command = cmd;
                SetCurrentContext(context);
                ORMTypeMappingHelper helper = this.MappingHelpers[ot];
                StringBuilder mySQL = new StringBuilder();
                mySQL.Append(
                    helper.SelectCommandText 
                    + " Where " 
                    + ot.KeyProperty.RuntimeDBFieldName + " = ");
                ORMDBCommand.AppendParameterSQL(
                    mySQL, 
                    ot.KeyProperty.RuntimeDBFieldName, 
                    keyValue,
                    this.Config.ParameterStyle ,
                    cmd );
                context.Command.CommandText = mySQL.ToString();
                this.BeforeExecuteCommand(context, context.Command);

                List<RelationPropertyValue> relations = new List<RelationPropertyValue>();
                IDataReader reader = cmd.ExecuteReader(
                    CommandBehavior.SingleResult
                    | CommandBehavior.SingleRow);

                if (cmd.UserCancel)
                {
                    SetCurrentContext(null);
                    return null;
                }
                else
                {
                    Array array = InnerReadInstances(context, reader, ot, relations, 1);
                    reader.Close();
                    if (array != null && array.Length > 0)
                    {
                        result = array.GetValue(0);
                        if (relations != null && relations.Count > 0)
                        {
                            ReadRelationInstance(context, relations);
                        }
                    }
                    SetCurrentContext(null);
                    return result;
                }
            }//using
        }

        public System.Array ReadAllInstances(Type instanceType)
        {
            if (instanceType == null)
            {
                throw new ArgumentNullException("instanceType");
            }
            ORMTypeInfo ot = this.Project.Types[ instanceType ] ;
            if (ot.Enabled == false)
            {
                return null;
            }
            using (ORMDBCommand cmd = new ORMDBCommand(this))
            {
                ORMTypeMappingHelper helper = this.MappingHelpers[ot];
                string strCommand = helper.OrderCommandText;
                if (Util.HasContent(strCommand))
                {
                    strCommand = " Order by " + strCommand;
                }
                strCommand = helper.SelectCommandText + strCommand;
                cmd.CommandText = strCommand;
                return ReadInstances(instanceType , cmd );
            }
        }

        public Array ReadInstances(Type instanceType, System.Collections.IList keyValues)
        {
            if (instanceType == null)
            {
                throw new ArgumentNullException("instanceType");
            }
            ORMTypeInfo ot = this.Project.Types[instanceType];
            if (ot.Enabled == false)
            {
                return null;
            }
            if (keyValues == null || keyValues.Count == 0)
            {
                return null;
            }
            if (keyValues.Count == 1)
            {
                object result = ReadInstance(instanceType, keyValues[0]);
                if (result != null)
                {
                    return new object[] { result };
                }
                else
                {
                    return null;
                }
            }
            else
            {
                using (ORMDBCommand cmd = new ORMDBCommand(this))
                {
                    ORMTypeMappingHelper helper = this.MappingHelpers[ot];
                    string strCommand = helper.OrderCommandText;
                    if (Util.HasContent(strCommand))
                    {
                        strCommand = " Order By " + strCommand;
                    }
                    strCommand = helper.SelectCommandText
                        + " Where " + ot.KeyProperty.RuntimeDBFieldName 
                        + " In ( " + Util.Contact( ot.KeyProperty.RuntimeDBValueType , keyValues ) + " ) " 
                        + strCommand;
                    cmd.CommandText = strCommand;
                    return ReadInstances(instanceType, cmd);
                }
            }
        }

        public System.Array ReadInstances( Type instanceType,ORMDBCommand cmd)
        {
            if (instanceType == null)
            {
                throw new ArgumentNullException("instanceType");
            }
            if (cmd == null)
            {
                throw new ArgumentNullException("cmd");
            }
            //CurrentCommand = dbcmd;
            ORMContext context = new ORMContext(this);
            context.Command = cmd;
            SetCurrentContext(context);
            this.BeforeExecuteCommand(context, context.Command);
            Array result = InnerReadInstances(instanceType , context);
            //CurrentCommand = null;
            SetCurrentContext(null);
            return result;
        }

        private System.Array InnerReadInstances( Type instanceType , ORMContext context )
        {
            if ( context == null )
            {
                throw new ArgumentNullException("context");
            }
            if (instanceType == null)
            {
                throw new ArgumentNullException("instanceType");
            }
            ORMTypeInfo ot = this.Project.Types[instanceType];
            if (ot == null)
            {
                throw new NotSupportedException(instanceType.FullName);
            }
            
            System.Collections.ArrayList list = new System.Collections.ArrayList();
            System.Data.IDataReader reader = context.Command.ExecuteReader(
                System.Data.CommandBehavior.SingleResult);

            if (context.Command.UserCancel)
            {
                return null;
            }
            else
            {
                List<RelationPropertyValue> relationValues = new List<RelationPropertyValue>();

                Array result = InnerReadInstances(context, reader, ot, relationValues, 0);
                reader.Close();
                if (relationValues.Count > 0)
                {
                    ReadRelationInstance(context, relationValues);
                }
                return result;
            }
        }

        /// <summary>
        /// Read instances without read relation sub instances
        /// </summary>
        /// <param name="reader">database data reader</param>
        /// <param name="type"></param>
        /// <param name="relationValues"></param>
        /// <param name="maxRecordCount"></param>
        /// <returns></returns>
        internal System.Array InnerReadInstances(
            ORMContext context ,
            IDataReader reader,
            ORMTypeInfo type,
            List<RelationPropertyValue> relationValues ,
            int maxRecordCount )
        {
            ORMPropertyInfo keyPropert = type.KeyProperty;
            ArrayList list = new ArrayList();
            ORMTypeMappingHelper helper = this.MappingHelpers[type];
            while (reader.Read())
            {
                if (maxRecordCount > 0 && list.Count >= maxRecordCount)
                {
                    break;
                }
                object instance = null;
                if (keyPropert != null)
                {
                    object keyValue = helper.GetDBValue(reader, keyPropert.Name);
                    if (keyValue != null && DBNull.Value.Equals(keyValue) == false)
                    {
                        if (this.IsBuffered(type))
                        {
                            // get instance from global buffer
                            instance = this.Buffer.GetInstance(
                                keyValue,
                                helper.InstanceType);
                            if (instance != null)
                            {
                                list.Add(instance);
                                continue;
                            }
                        }
                        // get instance from context buffer
                        instance = context.Buffer.GetInstance(keyValue, helper.InstanceType);
                        if (instance != null)
                        {
                            list.Add(instance);
                            continue;
                        }
                    }
                }
                // create new instance
                instance = helper.CreateInstance();
                helper.FillInstance(context , reader, instance, relationValues);
                if (instance is IORMInstance)
                {
                    IORMInstance oi = (IORMInstance)instance;
                    oi.ORMDataState = DataRowState.Unchanged;
                    oi.ORMEngine = this;
                }
                list.Add(instance);
                ORMPerformanceCounter.Increment(ORMPerformanceCounter.Name_Instance);
                ORMPerformanceCounter.IncreaseCreate(type.InstanceType);
                if ( keyPropert != null )
                {
                    object keyValue = helper.GetPropertyValue(instance, keyPropert.Name);// keyPropert.GetValue(instance);
                    if (keyValue != null && DBNull.Value.Equals(keyValue) == false)
                    {
                        if (this.IsBuffered(type))
                        {
                            // save instance to global buffer
                            ORMPerformanceCounter.IncreaseBuffered(type.InstanceType);
                            this.Buffer.Add(
                                helper.InstanceType,
                                keyValue,
                                instance);
                        }
                        // save instance to context buffer
                        context.Buffer.Add(
                            helper.InstanceType ,
                            keyValue,
                            instance);
                    }
                }
                // read next record
            }//while
            return list.ToArray(type.InstanceType);
        }
    
        private int ReadRelationInstance(
            ORMContext context ,
            List<RelationPropertyValue> relationValues )
        {
            int resultCount = 0;

            foreach (RelationPropertyValue item in relationValues)
            {
                
                ORMRelationStyle rs = item.Property.Relation.RuntimeStyle;
                if (rs == ORMRelationStyle.OneToMany
                    || rs == ORMRelationStyle.OneToManyWithKeyList)
                {
                    // get orm type of collection element
                    ORMTypeInfo type = this.Project.Types[
                        CollectionTypeHelper.GetElementType(item.Property.PropertyType)];
                    if (type.Enabled == false)
                    {
                        continue;
                    }
                    // create collection instance
                    object resultList = System.Activator.CreateInstance(
                        item.Property.PropertyType);
                    // get Add method info of collection
                    MethodInfo addMethod = CollectionTypeHelper.GetAddMethod(
                        item.Property.PropertyType);

                    ORMPropertyInfo childProperty = item.Property.Relation.ChildProperty;

                    ORMPropertyInfo keyProperty = type.KeyProperty;
                    if (childProperty == null)
                    {
                        childProperty = type.KeyProperty;
                    }

                    IDataReader reader = null;
                    if (rs == ORMRelationStyle.OneToMany)
                    {
                        ORMTypeMappingHelper helper = this.MappingHelpers[item.Property.OwnerType];
                        if (helper.FillDBCommand(
                            context,
                            helper.GetPropertyCommandText(item.Property.Name),
                            item.Instance,
                            item.Property.Name,
                            item.Value))
                        {
                            this.BeforeExecuteCommand(context, context.Command);

                            reader = context.Command.ExecuteReader(CommandBehavior.SingleResult);
                        }
                    }
                    else if (rs == ORMRelationStyle.OneToManyWithKeyList)
                    {
                        ORMTypeMappingHelper helper = this.MappingHelpers[item.Property.OwnerType];
                        if (Util.HasContent(item.Property.CommandText))
                        {
                            helper.FillDBCommand(
                                context,
                                helper.GetPropertyCommandText(item.Property.Name),
                                item.Instance,
                                item.Property.Name,
                                item.Value);
                        }
                        else if (keyProperty != null)
                        {
                            string strSQL = null;
                            string idlist = Convert.ToString( item.Value );
                            if (Util.HasContent(idlist) == false)
                            {
                                // id list is empty
                                continue;
                            }
                            strSQL = helper.SelectCommandText
                                + " Where " + keyProperty.RuntimeDBFieldName
                                + " In ( " + idlist + " )";
                            context.Command.CommandText = strSQL;
                            context.Command.Parameters.Clear();
                        }

                        this.BeforeExecuteCommand(context, context.Command);

                        reader = context.Command.ExecuteReader(CommandBehavior.SingleResult);
                    }
                    if (reader != null)
                    {
                        List<RelationPropertyValue> subRelations
                            = new List<RelationPropertyValue>();
                        Array list = InnerReadInstances(
                            context,
                            reader,
                            type,
                            subRelations,
                            0);
                        reader.Close();
                        foreach (object obj in list)
                        {
                            if ( this.IsBuffered( type ))
                            {
                                this.Buffer.AddOuterReference(obj, resultList, null);
                            }
                            addMethod.Invoke(resultList, new object[] { obj });
                        }

                        this.MappingHelpers[item.Property.OwnerType].SetPropertyValue(
                            item.Instance,
                            item.Property.Name,
                            resultList);

                        resultCount++;
                        if (subRelations.Count > 0)
                        {
                            resultCount += ReadRelationInstance(context, subRelations);
                        }
                    }
                }
                else if (rs == ORMRelationStyle.OneToOne)
                {
                    ORMTypeInfo type = item.Property.OwnerType;
                    if (type.Enabled == false)
                    {
                        continue;
                    }
                    List<RelationPropertyValue> subRelations
                                    = new List<RelationPropertyValue>();
                    object propertyValue = null;
                    ORMTypeMappingHelper helper = this.MappingHelpers[type];
                    if (Util.HasContent(item.Property.CommandText) == false)
                    {
                        // read instance from buffer
                        propertyValue = this.Buffer.GetInstance(item.Value, item.Property.PropertyType);
                    }
                    if (propertyValue == null)
                    {
                        if (helper.FillDBCommand(
                            context,
                            helper.GetPropertyCommandText(item.Property.Name),
                            item.Instance,
                            item.Property.Name,
                            item.Value))
                        {
                            this.BeforeExecuteCommand(context, context.Command);

                            IDataReader reader = context.Command.ExecuteReader(
                                CommandBehavior.SingleResult
                                | CommandBehavior.SingleRow);

                            if (reader != null)
                            {
                                ORMTypeInfo valueType = this.Project.Types[item.Property.PropertyType];

                                Array list = InnerReadInstances(
                                    context,
                                    reader,
                                    valueType,
                                    subRelations,
                                    1);
                                reader.Close();
                                if (list != null && list.Length > 0)
                                {
                                    propertyValue = list.GetValue(0);
                                }
                            }
                        }
                    }
                    if (propertyValue != null)
                    {
                        if ( this.IsBuffered( type ))
                        {
                            this.Buffer.AddOuterReference(
                                propertyValue,
                                item.Instance,
                                item.Property.Name);
                        }
                        this.MappingHelpers[item.Property.OwnerType].SetPropertyValue(
                           item.Instance,
                           item.Property.Name,
                           propertyValue);

                        //this.GetHelper( item.Property.OwnerType ).SetPropertyValue(
                        //    item.Instance ,
                        //    item.Property.Name ,
                        //    instance );

                        resultCount++;
                        if (subRelations.Count > 0)
                        {
                            resultCount += ReadRelationInstance(context, subRelations);
                        }
                    }//if
                }
            }//foreach

            return resultCount ;
        }

        #endregion


        /// <summary>
        /// Update instance to database
        /// </summary>
        /// <param name="instance"></param>
        /// <returns></returns>
        public int Update(object instance)
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }
            ORMTypeInfo type = this.Project.Types[instance.GetType()];
            if (type == null)
            {
                throw new NotSupportedException(instance.GetType().FullName);
            }
            if (type.Enabled == false)
            {
                return 0;
            }
            using (ORMDBCommand cmd = new ORMDBCommand(this))
            {
                ORMContext context = new ORMContext(this);
                context.Command = cmd;
                SetCurrentContext(context);
                ORMTypeMappingHelper helper = this.MappingHelpers[type];
                int result = 0;
                if (helper.PrepareUpdateCommand(context, instance))
                {
                    this.BeforeExecuteCommand(context, context.Command);
                    result = context.Command.ExecuteNonQuery();
                    if (result > 0)
                    {
                        this.EventSender.Broadcast(
                            this,
                            ORMEventArgs.CreateInstanceEvent(
                                context ,
                                instance,
                                ORMEventStyle.UpdateInstance ));
                        ORMPerformanceCounter.IncreaseUpdate(type.InstanceType);
                        result += InnerHandleRelationInstancesForInsertUpdate(context, instance, type, true);
                    }//if
                }//if
                SetCurrentContext(null);
                return result;
            }//using
        }

        private int InnerHandleRelationInstancesForInsertUpdate( ORMContext context , object instance, ORMTypeInfo type , bool insertAction )
        {
            int result = 0;
            ORMTypeMappingHelper helper = this.MappingHelpers[instance.GetType()];
            foreach (ORMPropertyInfo property in type.Properties)
            {
                if (property.Relation == null)
                {
                    continue;
                }
                ORMRelationInfo relation = property.Relation;
                if (relation.RuntimeStyle == ORMRelationStyle.OneToMany
                    && relation.UniteDeleteForUpdate)
                {
                    ORMTypeMappingHelper childHelper = this.MappingHelpers[CollectionTypeHelper.GetElementType(property.PropertyType)];
                    IEnumerable records = (IEnumerable)helper.GetPropertyValue(
                        instance, property.Name);
                    if (insertAction == false)
                    {
                        int deleted = InnerDeleteChildRecords(context, instance, property);
                        result += deleted;
                        ORMPerformanceCounter.IncreaseDelete(childHelper.InstanceType, deleted );
                    }
                    if (records != null)
                    {
                        foreach (object childInstance in records)
                        {
                            if (childHelper.PrepareInsertCommand(context, childInstance))
                            {
                                this.BeforeExecuteCommand(context, context.Command);
                                result = context.Command.ExecuteNonQuery();
                                if (result > 0)
                                {
                                    ORMPerformanceCounter.IncreaseUpdate(
                                        childHelper.InstanceType);
                                }//if
                            }//if
                        }//foreach
                    }//if
                }//if
                else if (relation.RuntimeStyle == ORMRelationStyle.OnetoManyExt)
                {
                    ORMTypeMappingHelper childHelper = this.MappingHelpers[CollectionTypeHelper.GetElementType(property.PropertyType)];
                    IEnumerable records = (IEnumerable)helper.GetPropertyValue(instance, property.Name);
                    result += this.StandardRelationManager.SaveChildRelations(
                        instance, 
                        childHelper.InstanceType,
                        records);
                }
            }//foreach
            return result;
        }

        /// <summary>
        /// Insert a instance into database
        /// </summary>
        /// <param name="instance"></param>
        /// <returns></returns>
        public int Insert(object instance)
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }
            ORMTypeInfo type = this.Project.Types[instance.GetType()];
            if (type == null)
            {
                throw new NotSupportedException(instance.GetType().FullName);
            }
            if (type.Enabled == false)
            {
                return 0;
            }
            using ( ORMDBCommand cmd = new ORMDBCommand( this ))
            {
                ORMContext context = new ORMContext(this);
                context.Command = cmd;
                SetCurrentContext(context);
                ORMTypeMappingHelper helper = this.MappingHelpers[type];
                int result = 0;
                if (helper.PrepareInsertCommand(context, instance))
                {
                    this.BeforeExecuteCommand(context, context.Command);
                    result = context.Command.ExecuteNonQuery();
                    if (result > 0)
                    {
                        this.EventSender.Broadcast(
                            this,
                            ORMEventArgs.CreateInstanceEvent(
                                context ,
                                instance,
                                ORMEventStyle.InsertInstance ));
                        if ( this.IsBuffered( type ))
                        {
                            this.Buffer.Add(
                                type.InstanceType,
                                helper.GetKeyValue(instance), 
                                instance);
                        }
                        ORMPerformanceCounter.IncreaseInsert(type.InstanceType);
                        result += InnerHandleRelationInstancesForInsertUpdate(context, instance, type, true);
                    }
                }
                SetCurrentContext(null);
                return result;
            }
        }
         
        #region Delete instance from data base

        /// <summary>
        /// Delete a instance 
        /// </summary>
        /// <param name="instance">instance</param>
        /// <returns>number of data base records has been deleted.</returns>
        public int Delete(object instance)
        {
            if( instance == null )
            {
                throw new ArgumentNullException("instance");
            }
            ORMTypeInfo type = this.Project.Types[ instance.GetType()] ;
            if( type == null )
            {
                throw new NotSupportedException( instance.GetType().FullName );
            }
            if (type.Enabled == false)
            {
                return 0;
            }
            using ( ORMDBCommand cmd = new ORMDBCommand( this ))
            {
                ORMContext context = new ORMContext( this );
                context.Command = cmd;
                SetCurrentContext(context);
                ArrayList deletedInstances = new ArrayList();
                int result = InnerDelete(context, instance, deletedInstances );
                if (deletedInstances.Count > 0)
                {
                    foreach (object obj in deletedInstances)
                    {
                        this.Buffer.DeleteOuterReference(obj, this);
                    }
                }
                SetCurrentContext(null);
                return result;
            }
        }

        private int InnerDelete(ORMContext context, object instance , ArrayList deletedInstances )
        {
            ORMTypeInfo type = this.Project.Types[instance.GetType()];
            if (type == null)
            {
                throw new NotSupportedException(instance.GetType().FullName);
            }
            ORMTypeMappingHelper helper = this.MappingHelpers[type];
            int result = 0;
            if (helper.PrepareDeleteCommand(context, instance))
            {
                this.BeforeExecuteCommand(context, context.Command);
                result = context.Command.ExecuteNonQuery();
                this.EventSender.Broadcast(
                    this, 
                    ORMEventArgs.CreateInstanceEvent(
                        context ,
                        instance,
                        ORMEventStyle.DeleteInstance));
            }
            if (result > 0)
            {
                ORMPerformanceCounter.IncreaseDelete(type.InstanceType);
                this.Buffer.Remove( instance);
                if (this.IsBuffered( type))
                {
                    deletedInstances.Add(instance);
                }
                foreach (ORMPropertyInfo property in type.Properties)
                {
                    if (property.Ignore || property.Relation == null)
                    {
                        continue;
                    }

                    ORMRelationStyle rs = property.Relation.RuntimeStyle;
                    if (rs == ORMRelationStyle.OnetoManyExt)
                    {
                        result += this.StandardRelationManager.SaveChildRelations(
                           instance,
                           CollectionTypeHelper.GetElementType( property.PropertyType ),
                           null);
                        continue;
                    }

                    if (property.Relation.UniteDeleteForDelete == false)
                    {
                        continue;
                    }
                    // delete relation instance
                    if ( rs == ORMRelationStyle.OneToOne)
                    {
                        object obj = helper.GetPropertyValue(instance, property.Name);
                        if (obj != null)
                        {
                            result += InnerDelete(context, obj , deletedInstances );
                        }
                    }
                    //else if (rs == ORMRelationStyle.OneToMany)
                    //{
                    //    result += InnerDeleteChildRecords(context, instance, property);
                    //}
                    else if (rs == ORMRelationStyle.OneToMany
                        || rs == ORMRelationStyle.OneToManyWithKeyList)
                    {
                        object list = helper.GetPropertyValue(instance, property.Name);
                        if (list != null)
                        {
                            if (list is System.Collections.IEnumerable)
                            {
                                foreach (object obj in (System.Collections.IEnumerable)list)
                                {
                                    result += InnerDelete(context, obj, deletedInstances);
                                }
                            }
                        }
                    }//end if
                }//foreach
            }//if
            return result;
        }

        private int InnerDeleteChildRecords( 
            ORMContext context ,
            object parentInstance,
            ORMPropertyInfo property)
        {
            ORMTypeInfo parentType = this.Project.Types[parentInstance.GetType()];
            ORMTypeMappingHelper helper = this.MappingHelpers[ parentType ] ;
            ORMTypeInfo childType = this.Project.Types[CollectionTypeHelper.GetElementType(property.PropertyType)];
            if (childType == null)
            {
                return 0;
            }
            context.Command.Parameters.Clear();
            StringBuilder str = new StringBuilder();
            str.Append("Delete From " + childType.TableName 
                + " Where " + property.Relation.ChildProperty.RuntimeDBFieldName + " = ");
            object keyValue = helper.GetKeyValue( parentInstance );
            ORMDBCommand.AppendParameterSQL( 
                str ,
                property.Relation.ChildPropertyName , 
                keyValue ,
                this.Config.ParameterStyle ,
                context.Command );
            context.Command.CommandText = str.ToString();
            this.BeforeExecuteCommand( context , context.Command );
            int result = context.Command.ExecuteNonQuery();
            return result ;
        }

        #endregion

        public void Dispose()
        {
            this._DataBase.Dispose();
            if (System.Threading.Thread.GetData(myDataStoreSlot) == this)
            {
                System.Threading.Thread.SetData(myDataStoreSlot, null);
            }
            this._EventSender.Clear();
            this._Buffer.Clear();
        }
    }
}